﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using IronPython;
using IronPython.Hosting;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using System.Reflection;
using ScriptEngine.Interface;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System.IO;

namespace ScriptEngine
{
    public class PythonEngine : ScriptEngine.IPythonEngine
    {
        private Microsoft.Scripting.Hosting.ScriptEngine engine;
        private IUnityContainer container;

        #region Singleton
        private static PythonEngine instance = new PythonEngine();
        public static PythonEngine Instance
        {
            get
            {
                return instance;
            }
        }
        #endregion

        #region .ctor
        private PythonEngine()
        {
            container = new UnityContainer().LoadConfiguration();

            engine = Python.CreateEngine(container.Resolve<PythonEngineOptions>().Options);

            engine.Runtime.LoadAssembly(Assembly.Load("mscorlib"));

            if (container.IsRegistered<IScriptCache>())
                container.RegisterType<IScriptCache>
                    (
                        new ContainerControlledLifetimeManager()
                    );
            else
                container.RegisterInstance<IScriptCache>
                    (
                        new ScriptCache(),
                        new ContainerControlledLifetimeManager()
                    );

        }

        #endregion

        #region Private Methods
        private string BuildStatement(string script, string[] imports)
        {
            string[] codeLines = script.Split("\r\n".ToCharArray());

            string funct = "funct" + script.GetHashCode().Abs() + "()";

            string statement = "";

            foreach (var import in imports)
            {
                statement += "\r\n";
                statement += String.Format("from {0} import *", import);
            }

            statement += "\r\ndef " + funct + ":";

            foreach (string line in codeLines)
            {
                if (!String.IsNullOrEmpty(line))
                    statement += "\r\n\t" + line;
            }

            statement += "\r\nContext.Value = " + funct;

            return statement;
        }
        private ScriptScope BuildScope(IScriptContext context)
        {
            ScriptScope scope = engine.CreateScope();

            scope.SetVariable("Context", context);

            return scope;
        }
        private ScriptSource CreateSource(string statement)
        {
            PythonEngineOptions options = container.Resolve<PythonEngineOptions>();

            if (options.Debug)
            {
                try
                {
                    using (FileStream stream = new FileStream(options.SourceCodePath, FileMode.Truncate))
                    {
                        using (StreamWriter writer = new StreamWriter(stream))
                        {
                            writer.Write(statement);
                        }
                    }

                    return engine.CreateScriptSourceFromFile(options.SourceCodePath);
                }
                catch (Exception x)
                {
                    return engine.CreateScriptSourceFromString(statement, SourceCodeKind.Statements);
                }
            }
            else
            {
                return engine.CreateScriptSourceFromString(statement, SourceCodeKind.Statements);
            }
        }
        #endregion

        #region Public Methods
        public static void IncludeAssemblies(Assembly[] assemblies)
        {
            if (instance == null)
                instance = new PythonEngine();

            foreach (var assembly in assemblies)
            {
                PythonEngine.Instance.engine.Runtime.LoadAssembly(assembly);
            }
        }


        public bool CheckSyntax(string script, IScriptContext context, string[] imports, ref string message)
        {
            string statement = BuildStatement(script, imports);

            ScriptSource source = CreateSource(statement);

            try
            {
                CompiledCode compiled = source.Compile();

                IScriptCache cache = container.Resolve<IScriptCache>();

                cache[script] = compiled;

                container.RegisterInstance<IScriptCache>
                (
                    cache,
                    new ContainerControlledLifetimeManager()
                );

                return true;
            }
            catch (SyntaxErrorException x)
            {
                message = x.Message;

                return false;
            }
        }
        public object Evaluate(string script, IScriptContext context, string[] imports)
        {

            string statement = BuildStatement(script, imports);

            ScriptScope scope = BuildScope(context);
       
            CompiledCode compiled = null;

            IScriptCache cache = container.Resolve<IScriptCache>();

            if ( cache[script] == null)
            {
                ScriptSource source = CreateSource(statement);
                
                compiled = source.Compile();

                cache[script] = compiled;

                container.RegisterInstance<IScriptCache>
                    (
                        cache,
                        new ContainerControlledLifetimeManager()
                    );
            }
            else
            {
                compiled = container.Resolve<IScriptCache>()[script];
            }

            compiled.Execute(scope);

            return scope.GetVariable("Context").Value;

        }
        #endregion
    }
}